﻿namespace NTLib.Pipe.Network.Providers
{
    using NTLib.Pipe.Providers;
    using System;
    using System.Threading.Tasks;
    using NTLib.Pipe.Descriptors;
    using System.Threading;
    using NTLib.Core.Attributes;
    using NTLib.Pipe.Network.Descriptors;
    using NTLib.Pipe.Containers;
    using System.Collections.Generic;
    using Exceptions;    
    
    /// <summary>
    /// Provide pipe map to network
    /// </summary>
    [Guid("fd673090-e94a-4aa3-835b-bffd3e0d0ab0")]
    public class NetworkPipeProvider : BasePipeProvider
    {
        #region Fields

        private readonly Guid _id = Guid.NewGuid();
        private readonly Dictionary<Guid, Tuple<NetworkTargetDescription, PipeType>> _pipeConnectedWithoutOut;

        #endregion

        #region Ctor

        /// <summary>
        /// Initializes a new instance of the <see cref="NetworkPipeProvider"/> class.
        /// </summary>
        public NetworkPipeProvider()
        {
            this._pipeConnectedWithoutOut = new Dictionary<Guid, Tuple<NetworkTargetDescription, PipeType>>();
        }

        #endregion

        /// <summary>
        /// Gets the identifier.
        /// </summary>
        public override Guid Id
        {
            get { return this._id; }
        }

        /// <summary>
        /// Determines whether this instance can managed target the specified target.
        /// </summary>
        /// <param name="desc">Target description used to define the target.</param>
        /// <param name="token"></param>
        /// <returns>
        ///   <c>true</c> if the provider can managed the specific target.
        /// </returns>
        public override Task<bool> CanManagedTargetAsync(ITargetDescriptor desc, CancellationToken token = default(CancellationToken))
        {
            return Task.FromResult(desc != null && desc is NetworkTargetDescription);
        }

        /// <summary>
        /// Called to create the specific pipe source associate the target.
        /// </summary>
        /// <param name="type">The type of the pipe.</param>
        /// <param name="target">The target of the pipe.</param>
        /// <param name="token">The cancel token.</param>
        /// <returns>IPipeSource that could managed object transfert in both ways.</returns>
        protected override async Task<IPipe<T>> OnCreateSourceAsync<T>(PipeType type, ITargetDescriptor target, CancellationToken token)
        {
            //NetworkPipeSource<T> source = new NetworkPipeSource<T>();
            //await source.InitializeAsync(target, type, token);

            //PipeUsingSource<T> pipe = new PipeUsingSource<T>();
            //pipe.SetSource(source, type);

            //return pipe;
            throw new NotImplementedException();
        }

        /// <summary>
        /// Gets the other side of the pipe from the pipe ID.
        /// </summary>
        /// <typeparam name="T">Type object that will travel through the pipe</typeparam>
        /// <param name="pipeId">The pipe identifier.</param>
        /// <param name="token"></param>
        /// <returns>
        /// Return asynchrounously the other side of the pipe from the pipe ID
        /// </returns>
        public override async Task<IPipeManipulator<T>> GetPipeAsync<T>(Guid pipeId, CancellationToken token = default(CancellationToken))
        {
            if (this._openPipes.ContainsKey(pipeId))
                throw new PipeSourceAlreadyPresentInThisProviderException();

            Tuple<NetworkTargetDescription, PipeType> target = null;
            if (this._pipeConnectedWithoutOut.TryGetValue(pipeId, out target))
            {
                var pipeWithSource = await this.OnCreateSourceAsync<T>(target.Item2, target.Item1, token);
                using (var lck = await this._lock.LockAsync(token))
                {
                    token.ThrowIfCancellationRequested();
                    this._openPipes.Add(pipeId, pipeWithSource);
                    this._pipeConnectedWithoutOut.Remove(pipeId);
                }
            }

            return await base.GetPipeAsync<T>(pipeId, token);
        }

        /// <summary>
        /// Registers an out less pipe.
        /// An out lessp pipe is a pipe waiting that something open the other side.
        /// </summary>
        /// <param name="id">The identifier.</param>
        /// <param name="targetInfo">The target information.</param>
        public async Task RegisterOutLessPipeAsync(Guid id, NetworkTargetDescription targetInfo, PipeType type)
        {
            using (var locker = await this._lock.LockAsync())
            {
                if (this._pipeConnectedWithoutOut.ContainsKey(id))
                    return;
                this._pipeConnectedWithoutOut.Add(id, new Tuple<NetworkTargetDescription, PipeType>(targetInfo, type));
            }
        }
    }
}
